/**
 * TetisDB
 *
 * Classe controladora do SnapShot
 */

package br.com.tetisdb.io.snapShot;

import br.com.tetisdb.io.snapShot.encoder.Encoder;
import br.com.tetisdb.lang.TetisObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.ObjectInputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;

import br.com.tetisdb.domain.TetisDomain;
import br.com.tetisdb.lang.TetisHashMap;

public class SnapShotManager extends TetisObject {
    //snapshot com os dados em memoria
    private TetisHashMap<String, Object> snapShot;
    //thread para auto-salvamento do snapshot
    private ThreadSnapShot thread;
    
    // <constantes>
    public static final String PIPE = "|";
    public static final String ATRIBUTOS = "TETIS.ATRIBUTOS";
    public static final String TIPOSATRIBUTOS = "TETIS.TIPOS";
    public static final String NOMECLASSE = "TETIS.NOMECLASSE";
    public static final String INDICE = "TETIS.INDICES";
    public static final String EXTENSAO_TETIS = ".tetis";
    // </constantes>
    
    // codificador de objetos em TetisHashMap
    private Encoder encoder;
    //domain que esse snapshot pertence
    private TetisDomain domain;
    
    /**
     * Construtor padrao
     *
     * @param domain TetisDomain cujo o SnapShotManager deve controlar
     */
    public SnapShotManager(TetisDomain domain) throws Exception {
        tetisLogger.info("Carregando SnapShot");
        
        this.domain = domain;
        snapShot = read();
        encoder = new Encoder(getSnapShot());
        tetisLogger.debug("SnapShot lido.");
        thread = new ThreadSnapShot(domain);
        thread.setDaemon(true);
        thread.start();
    }
    
    /**
     * Metodo para Parar a thread
     */
    public void pararThread() {
        if(thread != null) {
            tetisLogger.debug("Parando thread");
            thread.parar();
        }
    }
    
    /**
     * Metodo que forca a criacao de um snapshot.
     */
    public synchronized void takeSnapShot() throws Exception {
        if(thread != null) {
            tetisLogger.debug("Criando SnapShot");
            thread.takeSnapShot(snapShot);
        }
    }
    
    /**
     * Insere um objeto no SnapShot e o devolve
     *
     * @param object Objeto a ser inserido no SnapShot
     * @return Retorna o objeto apos inserir
     */
    public synchronized Object set(Object object) {
        if(object != null) {
            tetisLogger.debug("Inserindo objeto: "+object);
            encoder.encode(object);
            tetisLogger.debug("Objeto: "+object+" inserido!");
            return object;
        } else {
            return null;
        }
    }
    
    /**
     * Retorna o ID do snapshot formatado.
     *
     * @return Retorna uma String com o proximo ID para SnapShot.
     */
    public String getNextSnapShotID() {
        tetisLogger.debug("Criando proximo ID para SnapShot");
        NumberFormat formatter = new DecimalFormat("000000000000000");
        String s = getAtualID();
        
        long l = (s != null ? Long.parseLong(s) + 1 : 1L);
        
        return formatter.format(l);
    }
    
    /**
     * Le o ultimo snapshot e retorna o Map.
     */
    private TetisHashMap<String, Object> read() {
        tetisLogger.debug("Lendo SnapShot...");
        TetisHashMap<String, Object> mapa = new TetisHashMap<String, Object>();
        
        try {
            String snap = getAtualID();
            tetisLogger.debug("ID atual: "+snap);
            
            if (snap != null) {
                snap += EXTENSAO_TETIS;
                
                String path = getDomain().getCompleteDataPath();
                tetisLogger.debug("Path: "+path);
                File f = new File(path + File.separator + snap);
                
                if (f.exists()) {
                    FileInputStream fis = new FileInputStream(path + File.separator + snap);
                    ObjectInputStream ois = new ObjectInputStream(fis);
                    
                    tetisLogger.debug("Lendo...");
                    mapa = (TetisHashMap<String, Object>) ois.readObject();
                    tetisLogger.debug("SnapShot lido!");
                    
                    ois.close();
                    fis.close();
                }
            }
        } catch (Exception e) {
            tetisLogger.error("Erro ao ler SnapShot. Erro: "+e.getMessage(), e);
            tetisLogger.info("Retornando novo SnapShot.");
            return new TetisHashMap<String, Object>();
        }
        
        tetisLogger.debug("Leitura de SnapShot terminada.");
        
        return mapa;
    }
    
    /*
     * Retorna o id do ultimo snapshot criado.
     *
     * @return id Id do SnapShot atual.
     */
    private String getAtualID() {
        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return (name.endsWith(EXTENSAO_TETIS));
            }
        };
        
        File[] arquivos = new File(getDomain().getCompleteDataPath()).listFiles(filter);
        tetisLogger.debug("Encontrados "+arquivos.length+" snapshots");
        
        if (arquivos.length <= 0) {
            return null;
        }
        
        tetisLogger.debug("Ordenando snapshots...");
        Arrays.sort(arquivos);
        
        String s = arquivos[arquivos.length - 1].getName();
        s = s.substring(0, s.indexOf("."));
        tetisLogger.debug("ID selecionado: "+s);
        
        return s;
    }

    /**
     * Retorna o SnapShot em memoria
     *
     * @return Retorna o SnapShot
     */
    public TetisHashMap<String, Object> getSnapShot() {
        return snapShot;
    }
    
    /**
     * Retorna o TetisDomain do SnapShot em memoria
     *
     * @return domain TetisDomain do SnapShot em memoria
     */
    public TetisDomain getDomain() {
        return domain;
    }
    
    @Override
    public String toString() {
        return "SnapShotManager";
    }
}
